﻿//---*Authenticity Seal
/*			                                _ .--.
					                       ( `    )
									     .-'      `--,
			                  _..----.. (             )`-.
			                .'_|` _|` _|(  .__,           )
						   /_|  _|  _|  _(        (_,  .-'
			              ;|  _|  _|  _|  '-'__,--'`--'
			              | _|  _|  _|  _| |
			          _   ||  _|  _|  _|  _|
			        _( `--.\_|  _|  _|  _|/
			      .-'       )--,|  _|  _|.`
				(__, (_      ) )_|  _| /
			      `-.__.\ _,--'\|__|__/
			                    ;____;
			                     \  /
								  OO
			                     |""|
			                     '=='
			                     
           			 Autor: Federico Angel Donnet
*/
//---*Authenticity Seal
//---*Script para testear un character:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class PlayerScript : MonoBehaviour 
{
	//----------------//
	//---*Atributos:
	//----------------//

	//---------------------------------------------------------//
	//---*SubClase que se encarga de los estados del Character:
	//---------------------------------------------------------//
	[System.Serializable]
	public	class Body
	{
		//----------------//
		//---*Atributos:
		//----------------//
		//---*Enumerador de States:
		public	enum STATES
		{
			IDLE,
			RUN,
			JUMP,
			DIE
		};
		//---*Enumerador de Facing:
		public	enum FACING
		{
			LEFT,
			RIGHT,
		};
		//---*Referencia del Transform de este gameObject:
		[HideInInspector]
		public	Transform	theTransform			=	null;
		//---*Referencia del componente Animator de este character (gameObject):
		[HideInInspector]
		public	Animator	anim					=	null;
		//---*Estado actual del Character:
		public	STATES		currentState			=	STATES.IDLE;
		//---*Flag del Facing:
		[HideInInspector]
		public	FACING		facing					=	FACING.RIGHT;
		//---*Flag que indica si el sprite esta o no invertido en eje X:
		[HideInInspector]
		public	bool		flippedX				=	false;
		[HideInInspector]
		public	bool		fixFlipX				=	false;
		//---*Referencia de la clase "Locomotion":
		[HideInInspector]
		public	Locomotion	locomotion				=	null;

		//----------------//
		//---*Metodos:
		//----------------//
		//---*Inicia y prepara todo:
		public	void Init()
		{
			//---*Referenciamos al componente "Animator" de este GameObject en la subclase "Body":
			this.anim								=	this.theTransform.gameObject.GetComponent<Animator>();

		}
		//---*Invierte el Sprite Horizontalmente:
		public	void FlipX()
		{

			this.flippedX = !this.flippedX;
			//---*Para invertir un Sprite Horizontalmente solo multiplicamos la escala en X por -1:
			Vector3 theScale = this.theTransform.localScale;
			theScale.x 	*= -1;
			this.theTransform.localScale = theScale;
			this.fixFlipX = true;
		}

		public	void changeState(STATES newState)
		{
			this.currentState	=	newState;
		}


	};
	//---------------------------------------------------------//
	//---*SubClase que se encarga del Movimiento y Colisiones del Character:
	//---------------------------------------------------------//
	[System.Serializable]
	public	class Locomotion
	{
		//---*Enumerador de Collision Flags:
		public	enum COLFLAG
		{
			NONE	=	0,
			FLOOR	=	2,
			CEIL	=	8,
			LEFT	=	16,
			RIGHT	=	32,
			WALL	=	64,

		};
		//----------------//
		//---*Atributos:
		//----------------//
		//---*Referencia del Transform de este gameObject:
		[HideInInspector]
		public	Transform	theTransform			=	null;
		//---*Referencia del Body del gameObject:
		[HideInInspector]
		public	Body		body					=	null;
		//---*Fuerza que se le aplica al rb2D de este character para trasladarlo
		//---*Sobre el eje X:
		public float 		moveForce 				=	365.0f;			
		//---*Velocidad maxima de traslacion sobre el eje X:
		public float 		maxSpeedX				=	5.0f;	
		//---*Fuerza que se aplica al RigidBody2D al saltar:
		public float		jumpForce				=	320.0f;
		//---*Velocidad maxima de movimiento en eje Y:
		public float 		maxSpeedY 				=	5.0f;
		//---*Bandera que indica si va a saltar (Jump()):
		[HideInInspector]
		public bool			isJump					=	false;
		//---*Referencia del RigidBody2D de este Character:
		[HideInInspector]
		public	Rigidbody2D	rigidBody2D				=	null;
		//---*Otros atributos Auxiliares:
		//---*Mask del Piso ("Ground"):
		[HideInInspector]
		public	int			groundMask				=	0;
		[HideInInspector]
		public	COLFLAG		isTouch					=	COLFLAG.NONE;
		//---*Transform auxiliar para la plataforma movil:
		[HideInInspector]
		public	bool		isOnPlatform			=	false;

		//----------------//
		//---*Metodos:
		//----------------//
		//---*Init/Start 
		//---*Prepara todo lo necesario para el chequeo de colisiones:
		public	void Init()
		{
			//---*Referenciamos(almacenamos la ref) el RigidBody2D de este gameObject:
			this.rigidBody2D						=	this.theTransform.gameObject.GetComponent<Rigidbody2D>();
			//---*Seteamos los mask de colisiones de interes:
			this.groundMask							=	LayerMask.NameToLayer("Ground");
		}



		//---*Mueve Horizontalmente al Character:
		public	void Move(float hAxis)
		{
			//---*Mueve en Eje X mientras que se permita:
			if(CanMoveX(hAxis))
			{
				// If the player is changing direction (h has a different sign to velocity.x) or hasn't reached maxSpeed yet...
				if(hAxis * this.rigidBody2D.velocity.x < this.maxSpeedX)
				{
					//---*Empuja horizontalmente con la fuerza de movimiento:
					this.rigidBody2D.AddForce(Vector2.right * hAxis * this.moveForce);
				}

				//---*Si la velocidad supera la maxima permitida:
				if(Mathf.Abs(this.rigidBody2D.velocity.x) > this.maxSpeedX)
				{
					this.rigidBody2D.AddForce(Vector2.right * hAxis * this.moveForce);
				}
			}
			//---*Si la velocidad en eje X del RigidBody2D es mayor a la permitida (maxSpeed):
			if(Mathf.Abs(this.rigidBody2D.velocity.x) > this.maxSpeedX)
			{
				//---*Seteamos dicha velocidad en dicho valor:
				this.rigidBody2D.velocity = new Vector2(Mathf.Sign(this.rigidBody2D.velocity.x) * this.maxSpeedX, this.rigidBody2D.velocity.y);
			}
		}
		//---*Evalua si el Character puede moverce sobre eje X:
		bool CanMoveX(float hAxis)
		{
			//---*Swicheamos en base al facing del body:
			switch(this.body.facing)
			{
				case Body.FACING.LEFT:
					if(!isTouching(COLFLAG.LEFT))
					{
						return true;
					}
				break;
				case Body.FACING.RIGHT:
					if(!isTouching(COLFLAG.RIGHT))
					{
						return true;
					}
				break;
			}
			return false;
		}

		//---*Salta (movimiento en eje Y)
		public	void Jump()
		{
			//---*Aplica la fuerza de salto al rigidBody2D de este GO:
			this.rigidBody2D.AddForce(new Vector2(0.0f, this.jumpForce));					
		}

		//---*Ve si el flag de colision corresponde al ingresado
		public	bool isTouching(COLFLAG flag)
		{
			return (this.isTouch & flag) != 0;			
		}

	};

	//---*Instancia de la SubClase Body:
	public	Body			body					=	null;
	//---*Instancia de la SubClase Locomotion:
	public	Locomotion		locomotion				=	null;
	//---*BulletTime:
	private	bool			bulletTime				=	false;
	//---*Referencia del Transform del parent originario de este gameObject:
	[HideInInspector]
	public	Transform		originalParent			=	null;

	//----------------//
	//---*Metodos:
	//----------------//

	// Use this for initialization
	void Start () 
	{
		this.originalParent							=	this.gameObject.transform.parent;
		//---*Referenciamos el Transform root de este gameObject en body:
		this.body.theTransform						=	this.gameObject.transform;
		//---*Enviamos la referencia del locomotion al body:
		this.body.locomotion						=	this.locomotion;
		//---*Referenciamos el Transform root de este gameObject en locomotion:
		this.locomotion.theTransform				=	this.gameObject.transform;
		//---*Referenciamos el body de locomotion (asi se puede acceder al body desde locomotion):
		this.locomotion.body						=	this.body;
		//---*Iniciamos todo lo necesario del Locomotion para que este listo y preparado para detectar colisiones:
		this.locomotion.Init();
		//---*Iniciamos todo lo necesario del Body para que este listo y preparado para detectar colisiones:
		this.body.Init();
		//---*Por ultimo iniciamos en JUMP:
		ChangeToJUMPState();

	}
	
	void OnGUI()
	{
		GUI.Label (new Rect (0.0f,0.0f,200.0f,100.0f),"IsTouch: " + this.locomotion.isTouch.ToString());
		GUI.Label (new Rect (0.0f,15.0f,200.0f,100.0f),"Facing: " + this.body.facing.ToString());
		GUI.Label (new Rect (0.0f,30.0f,200.0f,100.0f),"Current State: " + this.body.currentState.ToString());
	}

	void Update()
	{

		if(Input.GetKeyDown(KeyCode.Q))
		{
			switch(this.bulletTime)
			{
				case true:
					Time.timeScale = 1.0f;
					this.bulletTime = false;
				break;
				case false:
					Time.timeScale = 0.30f;
					this.bulletTime = true;
				break;
			}
		}

		//---*Entra en la Maquina de Estados Finito:
		FSM();


	}

	void FixedUpdate ()
	{
		//---*Almacenamos el input Horizontal:
		float hAxis = Input.GetAxis("Horizontal");
		if(hAxis<0.0f && !this.body.flippedX)
		{
			this.body.facing = Body.FACING.LEFT;
			this.body.FlipX();
			
		}
		else if(hAxis>0.0f && this.body.flippedX)
		{
			this.body.facing = Body.FACING.RIGHT;
			this.body.FlipX();			
			
		}
		//---*Movimiento Horizontal:
		this.locomotion.Move(hAxis);
		//---*Movimiento Vertical:
		if(this.locomotion.isJump)
		{
			this.locomotion.Jump();
			ChangeToJUMPState();
			this.locomotion.isJump = false;
		}
		//---*Antes de salir del FixedUpdate, limpiamos los flags de colision:
		this.locomotion.isTouch = Locomotion.COLFLAG.NONE;
	}


	//----------------//
	//---*Maquina de Estados Finito:
	//----------------//
	void FSM()
	{
		//---*Almacenamos el input Horizontal:
		float hAxis = Input.GetAxis("Horizontal");
		//---*Swicheamos el estado actual de body:
		switch(this.body.currentState)
		{
			//---*Mientras que se encuentre en estado IDLE:
			case Body.STATES.IDLE:
				//---*Salto:
				if(Input.GetButtonDown("Jump") || Input.GetKeyDown(KeyCode.W) || Input.GetKeyDown(KeyCode.UpArrow))
				{
					//---*Si se encuentra sobre algo:
					if(this.locomotion.isTouching(Locomotion.COLFLAG.FLOOR))
					{
						//---*Salta:
						this.locomotion.isJump = true;		
						//this.locomotion.Jump();
						//ChangeToJUMPState();
						break;
					}
				}	
				if(hAxis!=0.0f)
				{

					switch(this.body.facing)
					{
						case Body.FACING.LEFT:
							if(!this.locomotion.isTouching(Locomotion.COLFLAG.LEFT))
							{
								ChangeToRUNState();
							}
						break;
						case Body.FACING.RIGHT:
							if(!this.locomotion.isTouching(Locomotion.COLFLAG.RIGHT))
							{
								ChangeToRUNState();
							}
					break;
					
					}
					
					
					
				}
				
				//---*Si no toca el piso:
				if(!this.locomotion.isTouching(Locomotion.COLFLAG.FLOOR))
				{
					ChangeToJUMPState();
				}
			break;
			//---*Mientras que se encuentre en estado RUN:
			case Body.STATES.RUN:
				//---*Si no se esta moviendo el input del axis:
				if(hAxis==0.0f || this.locomotion.isTouching(Locomotion.COLFLAG.WALL))
				{
					//---*Cambia el estado a IDLE:
					ChangeToIDLEState();
				}
				else 
				{
					
					//---*Salto:
					if(Input.GetButtonDown("Jump") || Input.GetKeyDown(KeyCode.W) || Input.GetKeyDown(KeyCode.UpArrow))
					{
						//---*Si se encuentra sobre algo:
						if(this.locomotion.isTouching(Locomotion.COLFLAG.FLOOR))
						{
							//---*Salta:
							this.locomotion.isJump = true;		
							//this.locomotion.Jump();
							//ChangeToJUMPState();
							break;
						}
					}
				}
				//---*Si no toca el piso:
				if(!this.locomotion.isTouching(Locomotion.COLFLAG.FLOOR))
				{
					ChangeToJUMPState();
				}
			break;
			//---*Mientras que se encuentre en estado JUMP:
			case Body.STATES.JUMP:
				
				//---*Si toca el piso, pasa a IDLE:
				if(this.locomotion.isTouching(Locomotion.COLFLAG.FLOOR))
				{
					//---*Si se esta precionando el imput de movimiento:
					if(hAxis!=0.0f)
					{
						ChangeToRUNState();
					}	
					//---*Si no se preciona nada, pasa a IDLE:
					else 
					{						
						ChangeToIDLEState();
					}
				}
				
			break;
			
		}		


	}

	//---*Cambia el estado a IDLE:
	void ChangeToIDLEState()
	{
		//---*Primero anulamos los parametros del Animator:
		this.body.anim.SetBool("isRun",false);
		this.body.anim.SetBool("isJump",false);
		//---*Activa el estado IDLE:
		this.body.anim.SetBool("isIdle",true);
		//---*Cambia el estado desde body:
		this.body.changeState(Body.STATES.IDLE);
	}
	//---*Cambia el estado a RUN:
	void ChangeToRUNState()
	{
		//---*Primero anulamos los parametros del Animator:
		this.body.anim.SetBool("isIdle",false);
		this.body.anim.SetBool("isJump",false);
		//---*Activa el estado RUN:
		this.body.anim.SetBool("isRun",true);
		//---*Cambia el estado desde body:
		this.body.changeState(Body.STATES.RUN);
	}
	//---*Cambia el estado a JUMP:
	void ChangeToJUMPState()
	{
		//---*Primero anulamos los parametros del Animator:
		this.body.anim.SetBool("isIdle",false);
		this.body.anim.SetBool("isRun",false);
		//---*Activa el estado JUMP:
		this.body.anim.SetBool("isJump",true);
		//---*Cambia el estado desde body:
		this.body.changeState(Body.STATES.JUMP);
		//---*Por ultimo vemos si el character se encuentra
		//---*sobre una plataforma, lo desparentamos:
		if(this.locomotion.isOnPlatform)
		{
			this.gameObject.transform.parent = this.originalParent;
			this.locomotion.isOnPlatform = false;
		}

	}
	//---*Cambia el estado a DIE:
	void ChangeToDIEState()
	{
		
	}

	//---*Chequeamos cuando este gameObject entra en colision:
	//---*Vemos de que "lado" hace "contacto" y determinamos
	//---*los flags de colision:
	void OnCollisionStay2D(Collision2D col)
	{


		//---*Recorremos todos los contactos:
		foreach (ContactPoint2D contact in col.contacts)
		{
			//Debug.Log(contact.normal.normalized);
			if(contact.normal.normalized == Vector2.up)
			{
				//---*Si al menos ya 1 point toca el suelo, se aniadie al bit:
				this.locomotion.isTouch |= Locomotion.COLFLAG.FLOOR;
				//Debug.Log("toca piso");
				//---*Vemos si es una plataforma movil:
				if(contact.collider.gameObject.layer == LayerMask.NameToLayer("MovingPlatform"))
				{
					OnMovingPlatformEnter(contact.collider.gameObject.transform);
				}
			}
			if(contact.normal.normalized == (Vector2.up*-1))
			{
				//---*Si al menos ya 1 point toca el techo, se aniadie al bit:
				this.locomotion.isTouch |= Locomotion.COLFLAG.CEIL;
				//Debug.Log("toca techo");
			}
			if(contact.normal.normalized == (Vector2.right*-1))
			{
				//---*Si al menos ya 1 point toca del lado izquierdo, se aniadie al bit:
				this.locomotion.isTouch |= Locomotion.COLFLAG.RIGHT;
				this.locomotion.isTouch |= Locomotion.COLFLAG.WALL;
				//Debug.Log("toca izq");
			}
			if(contact.normal.normalized == (Vector2.right))
			{
				//---*Si al menos ya 1 point toca del lado derecho, se aniadie al bit:
				this.locomotion.isTouch |= Locomotion.COLFLAG.LEFT;
				this.locomotion.isTouch |= Locomotion.COLFLAG.WALL;
				//Debug.Log("toca der");
			}
		}
		

	}

	void OnCollisionEnter2D(Collision2D col)
	{
		//---*Recorremos todos los contactos:
		foreach (ContactPoint2D contact in col.contacts)
		{
			//Debug.Log(contact.normal.normalized);
			if(contact.normal.normalized == Vector2.up)
			{
				//---*Si al menos ya 1 point toca el suelo, se aniadie al bit:
				this.locomotion.isTouch |= Locomotion.COLFLAG.FLOOR;
				//Debug.Log("toca piso");
				//---*Vemos si es una plataforma movil:
				if(contact.collider.gameObject.layer == LayerMask.NameToLayer("MovingPlatform"))
				{

					OnMovingPlatformEnter(contact.collider.gameObject.transform);
				}
			}
			if(contact.normal.normalized == (Vector2.up*-1))
			{
				//---*Si al menos ya 1 point toca el techo, se aniadie al bit:
				this.locomotion.isTouch |= Locomotion.COLFLAG.CEIL;
				//Debug.Log("toca techo");
			}
			if(contact.normal.normalized == (Vector2.right*-1))
			{
				//---*Si al menos ya 1 point toca del lado izquierdo, se aniadie al bit:
				this.locomotion.isTouch |= Locomotion.COLFLAG.RIGHT;
				this.locomotion.isTouch |= Locomotion.COLFLAG.WALL;
				//Debug.Log("toca izq");
			}
			if(contact.normal.normalized == (Vector2.right))
			{
				//---*Si al menos ya 1 point toca del lado derecho, se aniadie al bit:
				this.locomotion.isTouch |= Locomotion.COLFLAG.LEFT;
				this.locomotion.isTouch |= Locomotion.COLFLAG.WALL;
				//Debug.Log("toca der");
			}
		}
	}
	
	/*
	void OnCollisionExit2D(Collision2D coll) 
	{
		if(coll.gameObject.tag == "MovingPlatform")
		{
			this.locomotion.platform.gameObject.GetComponent<MovingPlatform>().target = null;
			this.locomotion.platform = null;
		}
		this.locomotion.isTouch = Locomotion.COLFLAG.NONE;
	}
	*/
	//---*CallBack que se llama al entrar en contacto con una plataforma Movil:
	void OnMovingPlatformEnter(Transform movingPlatform)
	{	
		//---*Si el character no se encuentra sobre una plataforma movil:
		if(!this.locomotion.isOnPlatform)
		{
			//---*Indicamos que desde este momento esta sobre una plataforma movil:
			this.locomotion.isOnPlatform = true;
			//---*Desde ahora el parent de este gameObject es la plataforma Movil:
			this.gameObject.transform.parent = movingPlatform;

		}
		//---*Si se encuentra sobre una plataforma, pero entra en contacto con otra:
		else if(this.transform.parent != movingPlatform)
		{
			//---*El character cambia de parent:
			this.gameObject.transform.parent = movingPlatform;
		}
	}
}
